//==============================================================================
// FASTAFile.cc
// -----------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
//
//   
//==============================================================================
//.......1.........2.........3.........4.........5.........6.........7.........8
//

#include "FASTAFile.hh"


//---------------------------------------------------
// * Read0
//---------------------------------------------------
// Older,slower, version of Read.
bool
FASTAFile::Read0(string FileName,SequenceVector &SeqVec)
{
  ifstream inFile;
  
  //char buffer[500];
  string tempLine;
  int  SeqNum = -1;
  bool bCommentLine = false;
  Sequence tempSeq;

  // Erase any existing sequence.
  SeqVec.clear();

  // Size need not be precise, just a guess at the size of a typical
  // sequence to get in the ball park and minimize the number of 
  // reallocations needed. 
  tempSeq.reserve(mExpectedSize);// KJD RESTORE THIS WITH VECTOR

  inFile.open(FileName.c_str(),ios::in);
  if (inFile.fail()){
    cout<<FileName<<" file open failed (Read Multiple FASTA)"<<endl;
    return(false);
  }

  do{
	 GetLine(inFile,tempLine);
    if (tempLine != ""){
      bCommentLine = (tempLine[0]=='>');
		
      // If it's a comment line that denotes another sequence
      if (bCommentLine){
		  tempSeq.mDescription = tempLine.substr(1,tempLine.size()-1);
		  SeqVec.push_back(tempSeq);
		  SeqNum++;
		  SeqVec.reserve(2000);
      }else{
		  // KJD BUG: Crashes if file empty!!!  No > == zero size SeqVec 
		  // Better error handling needed.
		  // Otherwise the line should contain a list of bases.
		  for(int charNum = 0;charNum< tempLine.size();charNum++){
			 // A little protection against extra cruft in the files.
			 if (IsBase(tempLine[charNum])){
				SeqVec[SeqNum].push_back(tempLine[charNum]);
			 }
		  }
      }
    }
  }while(!inFile.eof());
  int test = SeqVec[0].size();
  inFile.close();
  return(true);
}

//---------------------------------------------------
// * Read0
//---------------------------------------------------
//
bool
FASTAFile::Read0(string FileName,Sequence &Seq)
{
  // KJD Temporary solution... eventually will write single seq func
  SequenceVector SV;
  bool status = Read0(FileName,SV);
  Seq = SV[0];
  return(status);
}


//---------------------------------------------------
// * Read
//---------------------------------------------------
//
bool
FASTAFile::Read(string FileName,Sequence &Seq)
{
  // KJD Temporary solution... eventually will write single seq func
  SequenceVector SV;
  ifstream inFile;
  inFile.open(FileName.c_str(),ios::in);
  if (inFile.fail()){
    //cout<<FileName<<" file open failed (Read Multiple FASTA)"<<endl;
    return(false);
  }

  bool status = Read(inFile,SV);
  Seq = SV[0];
  inFile.close();
  return(status);
}


//---------------------------------------------------
// * ReadQual
//---------------------------------------------------
//
bool
FASTAFile::ReadQual(string FileName,SequenceQuality &Qual)
{
  SequenceQualityVector SQV;
  bool status = ReadQual(FileName,SQV);
  Qual = SQV[0];
  return(status);
}

//---------------------------------------------------
// * Read
//---------------------------------------------------
//
bool
FASTAFile::Read(string FileName,SequenceVector &SeqVec)
{
  ifstream inFile;
  inFile.open(FileName.c_str(),ios::in);
  if (inFile.fail()){
    cout<<FileName<<" file open failed (Read Multiple FASTA)"<<endl;
    return(false);
  }
  Read(inFile,SeqVec);
}


//---------------------------------------------------
// * Read
//---------------------------------------------------
//
bool
FASTAFile::Read(istream &fin,Sequence &Seq)
{
  // KJD Temporary solution... eventually will write single seq func
  SequenceVector SV;
  bool status = Read(fin,SV);
  Seq = SV[0];
  return(status);
}


//---------------------------------------------------
// * Append
//---------------------------------------------------
// This has been tested and is many times faster than 
// the implementation above, Read0.  
bool
FASTAFile::Append(istream &inFile,SequenceVector &SeqVec)
{
  int  SeqNum = SeqVec.size() - 1;
  int expectedSize = mExpectedSize;  
  char buffer[5000];
 
  // Erase any existing sequence, just in case.  
  //SeqVec.clear();
  //SeqVec.resize(300);

  do{
    if (inFile.getline(buffer,4900)){
      // If it's a comment line that denotes another sequence
		if (buffer[0]=='>'){
		  SeqNum++;
		  if (SeqNum>=SeqVec.size()){
			 SeqVec.resize(SeqVec.size()+300);
		  }
		  string fullDesc = (buffer+1);
		  int offset = 0;
		  // Everything up to first white space is name, after that is description. 
		  // KJD Verify boundaries. 
		  SeqVec[SeqNum].mName = GetNextWSDelimitedString(fullDesc,offset,offset);
		  // It's possible there is no description, so check:
		  if ((offset >= 0) && (offset < fullDesc.size())){
			 SeqVec[SeqNum].mDescription = fullDesc.substr(offset,fullDesc.size()-offset);
		  }else{
			 SeqVec[SeqNum].mDescription = "";
		  }	  
		  SeqVec[SeqNum].reserve(expectedSize);
      }else{
		  register int sEnd = strlen(buffer)-1;
		  // Sadly, many FASTA files have empty lines or whitespace at the 
		  // end of lines.  This is effectively filtered here by adding a null
		  // terminator. 
		  if ((buffer[sEnd] == '\t') || 
				(buffer[sEnd] == ' ') ||
				(buffer[sEnd]=='\n')) {
			 buffer[sEnd--]='\0';
		  }

		  // Copy the characters of the line to the sequence vector. 
		  SeqVec[SeqNum].ConcatenateCString(buffer);			                
      }
    }
  }while(!inFile.eof());
  // In case we overshot and made SeqVec too big, need to fix it's 
  // size appropriately.
  SeqVec.resize(SeqNum+1);

  if (SeqVec.size() == 0) return(false);
  else return(true);
}


//----------------------------------------
// * GetNextRead
//----------------------------------------
// Gets the next read from a file. 
// buffer[0] should be set to '1' for the first call of this function.  On subsequent calls, 
// buffer will contain the comment line for the next read in the file.  
// 
bool 
FASTAFile::GetNextRead(istream &fin,char *buffer,int bufSize,Sequence &Seq)
{  
  // Erase any existing sequence, just in case.  
  Seq.clear();
  
  // Must be first line  
  if (buffer[0] == '1'){
	 fin.getline(buffer,bufSize);
  }
  
  // Extract name and description from line.  
  string nextDesc = buffer;
  int offset = 1; // To skip >
  // Everything up to first white space is name, after that is description. 
  Seq.mName = GetNextWSDelimitedString(nextDesc,offset,offset);  
  // It's possible there is no description, so check:
  if ((offset >= 0) && (offset < nextDesc.size())){
	 Seq.mDescription = nextDesc.substr(offset,nextDesc.size()-offset);
  }else{
	 Seq.mDescription = "";
  }
  
  // Get the sequence until another > is encountered. 
  do{
	 fin.getline(buffer,bufSize);
	 string buf = buffer;
	 int bytesRead = fin.gcount();
	 int bsize = strlen(buffer);
	 // This means that we have a null before the newline, so I abort this
	 // probably corrupt read. 
	 if ((bsize < (bytesRead-1)) && (bsize !=0) && (bytesRead != 0)) return(false);
	 
	 if (buffer[0] != '>'){
		// Sadly, many FASTA files have empty lines or whitespace at the 
		// end of lines.  This is effectively filtered here by adding a null
		// terminator in place of all whitespace. 
		
		register int sEnd = bsize-1;
		while((buffer[sEnd] == '\t') || 
				(buffer[sEnd] == ' ') ||
				(buffer[sEnd]=='\n') && (sEnd > 0)){
		  buffer[sEnd--] = '\0';
		}

		// Copy the characters of the line to the sequence vector. 
		
		Seq.ConcatenateCString(buffer);			            
	 }
  }while((!fin.eof()) && (buffer[0] != '>'));
  return(true);
}





//---------------------------------------------------
// * Read
//---------------------------------------------------
// This has been tested and is many times faster than 
// the implementation above, Read0.  
bool
FASTAFile::Read(istream &inFile,SequenceVector &SeqVec)
{
  int  SeqNum = -1;
  int expectedSize = mExpectedSize;  
  char buffer[5000];
 
  // Erase any existing sequence, just in case.  
  SeqVec.clear();
  SeqVec.resize(300);

  do{
    if (inFile.getline(buffer,4900)){
      // If it's a comment line that denotes another sequence
		if (buffer[0]=='>'){
		  //SeqVec.push_back(tempSeq);
		  SeqNum++;
		  if (SeqNum>=SeqVec.size()){
			 SeqVec.resize(SeqVec.size()+300);
		  }
		  string fullDesc = (buffer+1);
		  int offset = 0;
		  // Everything up to first white space is name, after that is description. 
		  // KJD Verify boundaries. 
		  SeqVec[SeqNum].mName = GetNextWSDelimitedString(fullDesc,offset,offset);
		  // It's possible there is no description, so check:
		  if ((offset >= 0) && (offset < fullDesc.size())){
			 SeqVec[SeqNum].mDescription = fullDesc.substr(offset,fullDesc.size()-offset);
		  }else{
			 SeqVec[SeqNum].mDescription = "";
		  }	  
		  SeqVec[SeqNum].reserve(expectedSize);
      }else{
		  register int sEnd = strlen(buffer)-1;
		  // Sadly, many FASTA files have empty lines or whitespace at the 
		  // end of lines.  This is effectively filtered here by adding a null
		  // terminator. 
		  if ((buffer[sEnd] == '\t') || 
				(buffer[sEnd] == ' ') ||
				(buffer[sEnd]=='\n')) {
			 buffer[sEnd--]='\0';
		  }

		  // Copy the characters of the line to the sequence vector. 
		  SeqVec[SeqNum].ConcatenateCString(buffer);			                
      }
    }
  }while(!inFile.eof());
  // In case we overshot and made SeqVec too big, need to fix it's 
  // size appropriately.
  SeqVec.resize(SeqNum+1);

  if (SeqVec.size() == 0) return(false);
  else return(true);
}

//---------------------------------------------------
// * ReadQualX
//---------------------------------------------------
bool
FASTAFile::ReadQualX(istream &inFile,SequenceQualityVector &QualVec)
{
   string tempLine,qualStr;
  int  QualNum = -1;
  int  qual;
  bool bCommentLine = false;
  SequenceQuality tempQual;

  // Erase any previous contents.  
  QualVec.erase(QualVec.begin(),QualVec.end());

  // Size need not be precise, just a guess at the size of a typical
  // sequence to get in the ball park and minimize the number of 
  // reallocations needed. 
  tempQual.reserve(mExpectedSize);

  do{
	 GetLine(inFile,tempLine);
    //inFile.getline(buffer,500);
    //tempLine = buffer;  // make it into a string for find,etc.
    if (tempLine != ""){
      bCommentLine = (tempLine[0]=='>');

      // If it's a comment line that denotes another sequence
      if (bCommentLine){
		  string fullDesc = tempLine.substr(1,tempLine.size()-1);		  
		  int offset = 0;
		  tempQual.mName = GetNextWSDelimitedString(fullDesc,offset,offset);
		  
		  if ((offset >= 0) && (offset < fullDesc.size())){
			 tempQual.mDescription = fullDesc.substr(offset,fullDesc.size()-offset);
		  }else{
			 tempQual.mDescription = "";
		  }	  
		  QualVec.push_back(tempQual);
		  QualNum++;
      }else{
		  // Otherwise the line should contain a list of quality values
		  GetIntsFromLine(tempLine,QualVec[QualNum]);
      }
    }
  }while(!inFile.eof());
  return(true);
}


//---------------------------------------------------
// * ReadQual
//---------------------------------------------------
bool
FASTAFile::ReadQual(istream &inFile,SequenceQualityVector &QualVec)
{
  int  QualNum = -1;
  int expectedSize = mExpectedSize;  
  char buffer[5000];
 
  // Erase any existing sequence, just in case.  
  QualVec.clear();
  QualVec.resize(300);

  do{
    if (inFile.getline(buffer,4900)){
      // If it's a comment line that denotes another sequence
		if (buffer[0]=='>'){
		  //SeqVec.push_back(tempSeq);
		  QualNum++;
		  if (QualNum>=QualVec.size()){
			 QualVec.resize(QualVec.size()+300);
		  }
		  string fullDesc = (buffer+1); // String minus the >
		  int offset = 0;
		  QualVec[QualNum].mName = GetNextWSDelimitedString(fullDesc,offset,offset);
		  if ((offset >= 0) && (offset < fullDesc.size())){
			 QualVec[QualNum].mDescription = fullDesc.substr(offset,fullDesc.size()-offset);
		  }else{
			 QualVec[QualNum].mDescription = "";
		  }	  
		  QualVec[QualNum].reserve(expectedSize);
      }else{
		  // Otherwise the buffer should contain a list of ints.
		  string temp = buffer;
		  if (!ContainsOnlyWS(temp)){
			 GetIntsFromLine(buffer,QualVec[QualNum]);
		  }else{
			 cout<<"Hey why is there white space here?"<<endl;
		  }
      }
    }
  }while(!inFile.eof());

  // In case we overshot and made SeqVec too big, need to fix it's 
  // size appropriately.
  QualVec.resize(QualNum+1);

  if (QualVec.size() == 0) return(false);
  else return(true);
}






//---------------------------------------------------
// * ReadQual
//---------------------------------------------------
bool
FASTAFile::ReadQual(string FileName,SequenceQualityVector &QualVec)
{
  ifstream inFile;
  int  QualNum = -1;
  int expectedSize = mExpectedSize;  
  char buffer[5000];
 
  // Erase any existing sequence, just in case.  
  QualVec.clear();
  QualVec.resize(300);

  inFile.open(FileName.c_str(),ios::in);
  if (inFile.fail()){
    cout<<FileName<<" file open failed (Read Multiple FASTA)"<<endl;
    return(false);
  }

  do{
    if (inFile.getline(buffer,4900)){
      // If it's a comment line that denotes another sequence
		if (buffer[0]=='>'){
		  //SeqVec.push_back(tempSeq);
		  QualNum++;
		  if (QualNum>=QualVec.size()){
			 QualVec.resize(QualVec.size()+300);
		  }
		  string fullDesc = (buffer+1); // String minus the >
		  int offset = 0;
		  QualVec[QualNum].mName = GetNextWSDelimitedString(fullDesc,offset,offset);
		  if ((offset >= 0) && (offset < fullDesc.size())){
			 QualVec[QualNum].mDescription = fullDesc.substr(offset,fullDesc.size()-offset);
		  }else{
			 QualVec[QualNum].mDescription = "";
		  }	  
		  QualVec[QualNum].reserve(expectedSize);
      }else{
		  // Otherwise the buffer should contain a list of ints.
		  GetIntsFromLine(buffer,QualVec[QualNum]);
      }
    }
  }while(!inFile.eof());

  // In case we overshot and made SeqVec too big, need to fix it's 
  // size appropriately.
  QualVec.resize(QualNum+1);

  inFile.close();
  if (QualVec.size() == 0) return(false);
  else return(true);
}

//---------------------------------------------------
// * ReadQual
//---------------------------------------------------
//
bool
FASTAFile::ReadQual0(string FileName,SequenceQualityVector &QualVec)
{
  ifstream inFile;
  
  //char buffer[500];
  string tempLine,qualStr;
  int  QualNum = -1;
  int  qual;
  bool bCommentLine = false;
  SequenceQuality tempQual;

  // Erase any previous contents.  
  QualVec.erase(QualVec.begin(),QualVec.end());

  // Size need not be precise, just a guess at the size of a typical
  // sequence to get in the ball park and minimize the number of 
  // reallocations needed. 
  tempQual.reserve(mExpectedSize);

  inFile.open(FileName.c_str(),ios::in);
  if (inFile.fail()){
    cout<<FileName<<" file open failed"<<endl;
    return(false);
  }

  do{
	 GetLine(inFile,tempLine);
    //inFile.getline(buffer,500);
    //tempLine = buffer;  // make it into a string for find,etc.
    if (tempLine != ""){
      bCommentLine = (tempLine[0]=='>');

      // If it's a comment line that denotes another sequence
      if (bCommentLine){
		  tempQual.mDescription = tempLine.substr(1,tempLine.size()-1);
		  QualVec.push_back(tempQual);
		  QualNum++;
      }else{
		  // Otherwise the line should contain a list of quality values
		  GetIntsFromLine(tempLine,QualVec[QualNum]);
      }
    }
  }while(!inFile.eof());
  return(true);
}

//---------------------------------------------------
// * Write
//---------------------------------------------------
//
void
FASTAFile::Write(string FileName,SequenceVector &SeqVec)
{
  ofstream outFile;

  //outFile.open(FileName.c_str(),ios::out); KJD Doesn't work on mac (???)
  outFile.open(FileName.c_str());
  if (outFile.fail()){
    cout<<FileName<<" file open failed"<<endl;
    return;
  }
  Write(outFile,SeqVec);
  outFile.close();
}

//---------------------------------------------------
// * Write
//---------------------------------------------------
//
void
FASTAFile::Write(ostream &outFile,SequenceVector &SeqVec)
{
  int baseNum,seqNum;
  for(seqNum = 0;seqNum<SeqVec.size();seqNum++){
 	 outFile<<">"<<SeqVec[seqNum].mName<<" "<<SeqVec[seqNum].mDescription<<"\n";
    for(baseNum = 0;baseNum<SeqVec[seqNum].size();baseNum++){
      outFile<<SeqVec[seqNum][baseNum];
      if (((baseNum+1) % mSeqLineSize) == 0){
			outFile<<"\n";
      }
    }
    // Except in the rare case where the line ended on an even 
    // multiple of 50, and an endl is already present, put in a 
    // line feed. 
    if ((baseNum %mSeqLineSize) != 0){
      outFile<<"\n";
    }
  } 
}

//---------------------------------------------------
// * Write
//---------------------------------------------------
//
void
FASTAFile::Write(ostream &outFile,Sequence &Seq)
{
  SequenceVector SV;
  SV.push_back(Seq);
  Write(outFile,SV);
}


//---------------------------------------------------
// * Write
//---------------------------------------------------
//
void
FASTAFile::Write(string FileName,Sequence &Seq)
{

  ofstream outFile;

  //outFile.open(FileName.c_str(),ios::out); KJD Doesn't work on mac (???)
  outFile.open(FileName.c_str());
  if (outFile.fail()){
    cout<<FileName<<" file open failed"<<endl;
    return;
  }
  SequenceVector SV;
  SV.push_back(Seq);
  Write(outFile,SV);
  outFile.close();
}

//---------------------------------------------------
// * WriteQual
//---------------------------------------------------
//
void
FASTAFile::WriteQual(string FileName,SequenceQualityVector &QualVec)
{
  ofstream outFile;

  outFile.open(FileName.c_str(),ios::out);
  if (outFile.fail()){
    cout<<FileName<<" file open failed"<<endl;
    return;
  }
	int qualNum,baseNum;
  for(qualNum = 0;qualNum<QualVec.size();qualNum++){
    outFile<<">"<<QualVec[qualNum].mName<<" "<<QualVec[qualNum].mDescription<<endl;	
    for(baseNum = 0;baseNum<QualVec[qualNum].size();baseNum++){
      outFile<<QualVec[qualNum][baseNum];
      if (((baseNum+1) % mQualLineSize) == 0){
		  outFile<<endl;
      }else{
		  outFile<<" ";
      }
    }
	 
    // Except in the rare case where the line ended on an even 
    // multiple of 25, and an endl is already present, put in a 
    // line feed. 
    if ((baseNum %mQualLineSize) != 0){
      outFile<<endl;
    }
  }
  outFile.close();
}

//---------------------------------------------------
// * WriteQual
//---------------------------------------------------
//
void 
FASTAFile::WriteQual(string FileName,SequenceQuality &Qual)
{
  SequenceQualityVector SQV;
  SQV.push_back(Qual);
  WriteQual(FileName,SQV);
}


//---------------------------------------------------
// * WriteQual
//---------------------------------------------------
//
void
FASTAFile::WriteQual(ostream &outFile,SequenceQualityVector &QualVec)
{
  int qualNum,baseNum;
  for(qualNum = 0;qualNum<QualVec.size();qualNum++){
    outFile<<">"<<QualVec[qualNum].mName<<" "<<QualVec[qualNum].mDescription<<endl;	
    for(baseNum = 0;baseNum<QualVec[qualNum].size();baseNum++){
      outFile<<QualVec[qualNum][baseNum];
      if (((baseNum+1) % mQualLineSize) == 0){
		  outFile<<endl;
      }else{
		  outFile<<" ";
      }
    }
	 
    // Except in the rare case where the line ended on an even 
    // multiple of 25, and an endl is already present, put in a 
    // line feed. 
    if ((baseNum %mQualLineSize) != 0){
      outFile<<endl;
    }
  }
}


//---------------------------------------------------
// * WriteQual
//---------------------------------------------------
//
void
FASTAFile::WriteQual(ostream &outFile,SequenceQuality &Qual)
{
  int qualNum,baseNum;
  outFile<<">"<<Qual.mName<<" "<<Qual.mDescription<<endl;
  

  for(baseNum = 0;baseNum<Qual.size();baseNum++){
	 outFile<<Qual[baseNum];
	 if (((baseNum+1) % mQualLineSize) == 0){
		outFile<<endl;
	 }else{
		outFile<<" ";
	 }
  }
	 
  // Except in the rare case where the line ended on an even 
  // multiple of 25, and an endl is already present, put in a 
  // line feed. 
  if ((baseNum %mQualLineSize) != 0){
	 outFile<<endl;
  }
}



